Nabla

In \(\mathbb{R}^3\) the nabla operator is defined as: \[ \nabla=\mathbf{e}_x \frac{\partial}{\partial x}+\mathbf{e}_y \frac{\partial}{\partial y}+\mathbf{e}_z \frac{\partial}{\partial z}=\left[\frac{\partial}{\partial x}, \frac{\partial}{\partial y}, \frac{\partial}{\partial z}\right] \] where \(\mathbf{e}_x\), \(\mathbf{e}_y\) and \(\mathbf{e}_z\) are the unit vectors in the \(x\), \(y\) and \(z\) directions respectively, i.e. standard basis.

Gradient, Divergence and Curl

The gradient of a scalar field \(f(x,y,z)\) is a vector field defined as: \[ \nabla f = \left[\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}, \frac{\partial f}{\partial z}\right] \]

The divergence of a vector field \(\mathbf{F}(x,y,z)\) is a scalar field defined as: \[ \nabla \cdot \mathbf{F} = \frac{\partial F_x}{\partial x} + \frac{\partial F_y}{\partial y} + \frac{\partial F_z}{\partial z} \]

The curl of a vector field \(\mathbf{F}(x,y,z)\) is a vector field defined as a determinant: \[ \nabla \times \mathbf{F} = \left[\frac{\partial}{\partial x}, \frac{\partial}{\partial y}, \frac{\partial}{\partial z}\right] \times \begin{vmatrix} \mathbf{e}_x & \mathbf{e}_y & \mathbf{e}_z \\ \frac{\partial}{\partial x} & \frac{\partial}{\partial y} & \frac{\partial}{\partial z} \\ F_x & F_y & F_z \end{vmatrix} \]

All these operators are linear, which are important properties for solving differential equations, that we know the linear combination of two solutions is also a solution.

Linear Properties of Nabla

The nabla operator has the following linear properties, such that for any scalar field \(f\) and \(g\) and any constant \(c\), for gradient: \[ \nabla(f+g)=\nabla f+\nabla g\\ \nabla(c f)=c \nabla f \]

import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots


# Define the vector field F(x, y, z) = (yz, xz, xy + exp(-x^2 - y^2 - z^2))
def vector_field(x, y, z):
    """Vector field F(x, y, z) = (yz, xz, xy + exp(-x^2 - y^2 - z^2))"""
    F_x = y * z
    F_y = x * z
    F_z = x * y + np.exp(-(x**2) - y**2 - z**2)
    return np.array([F_x, F_y, F_z])


# Calculate Gradient (for visualization purposes)
def divergence(x, y, z, h=1e-5):
    """Calculate the divergence of the vector field using finite differences."""
    dFxdx = (vector_field(x + h, y, z)[0] - vector_field(x - h, y, z)[0]) / (2 * h)
    dFydy = (vector_field(x, y + h, z)[1] - vector_field(x, y - h, z)[1]) / (2 * h)
    dFzdz = (vector_field(x, y, z + h)[2] - vector_field(x, y, z - h)[2]) / (2 * h)
    return dFxdx + dFydy + dFzdz


def curl(x, y, z, h=1e-5):
    """Calculate the curl of the vector field using finite differences."""
    dFz_dy = (vector_field(x, y + h, z)[2] - vector_field(x, y - h, z)[2]) / (2 * h)
    dFy_dz = (vector_field(x, y, z + h)[1] - vector_field(x, y, z - h)[1]) / (2 * h)
    dFx_dz = (vector_field(x, y, z + h)[0] - vector_field(x, y, z - h)[0]) / (2 * h)
    dFz_dx = (vector_field(x + h, y, z)[2] - vector_field(x - h, y, z)[2]) / (2 * h)
    dFy_dx = (vector_field(x + h, y, z)[1] - vector_field(x - h, y, z)[1]) / (2 * h)
    dFx_dy = (vector_field(x, y + h, z)[0] - vector_field(x, y - h, z)[0]) / (2 * h)

    curl_x = dFz_dy - dFy_dz
    curl_y = dFx_dz - dFz_dx
    curl_z = dFy_dx - dFx_dy
    return np.array([curl_x, curl_y, curl_z])


# Generate 3D grid points
x_vals = np.linspace(-2, 2, 20)  # Adjust the range for better visualization
y_vals = np.linspace(-2, 2, 20)
z_vals = np.linspace(-2, 2, 20)
X, Y, Z = np.meshgrid(x_vals, y_vals, z_vals)

# Initialize arrays for vector field, divergence, and curl
vector_values = np.zeros((X.size, 3))
divergence_values = np.zeros(X.size)
curl_vectors = np.zeros((X.size, 3))

# Calculate field values
for i in range(X.size):
    x, y, z = X.flatten()[i], Y.flatten()[i], Z.flatten()[i]

    # Calculate Vector Field
    vec = vector_field(x, y, z)
    vector_values[i] = vec

    # Calculate Divergence
    div = divergence(x, y, z)
    divergence_values[i] = div

    # Calculate Curl
    crl = curl(x, y, z)
    curl_vectors[i] = crl

# Prepare Plotly figures
fig = make_subplots(
    rows=1,
    cols=3,
    specs=[[{"type": "scatter3d"}, {"type": "scatter3d"}, {"type": "scatter3d"}]],
    subplot_titles=["Vector Field", "Divergence", "Curl"],
)

# Vector Field Plot
fig.add_trace(
    go.Cone(
        x=X.flatten(),
        y=Y.flatten(),
        z=Z.flatten(),
        u=vector_values[:, 0],
        v=vector_values[:, 1],
        w=vector_values[:, 2],
        sizemode="absolute",
        sizeref=0.5,
        colorscale="Blues",
    ),
    row=1,
    col=1,
)

# Divergence Field Plot
fig.add_trace(
    go.Scatter3d(
        x=X.flatten(),
        y=Y.flatten(),
        z=Z.flatten(),
        mode="markers",
        marker=dict(
            size=4,
            color=divergence_values,
            colorscale="Reds",
            colorbar=dict(title="Divergence"),
        ),
    ),
    row=1,
    col=2,
)

# Curl Field Plot
fig.add_trace(
    go.Cone(
        x=X.flatten(),
        y=Y.flatten(),
        z=Z.flatten(),
        u=curl_vectors[:, 0],
        v=curl_vectors[:, 1],
        w=curl_vectors[:, 2],
        sizemode="absolute",
        sizeref=0.5,
        colorscale="Greens",
    ),
    row=1,
    col=3,
)

# Update Layout
fig.update_layout(
    height=600,
    width=1800,
    title_text="3D Visualization of Vector Field, Divergence, and Curl",
    showlegend=False,
)
fig.show()